package com.bw.itext.pdf.service;

import cn.hutool.core.bean.BeanUtil;
import com.bw.itext.pdf.PdfProperties;
import com.bw.itext.pdf.annotation.PdfValue;
import com.bw.itext.pdf.utils.DeferredSignPdfAssLry;
import com.bw.itext.pdf.utils.EmbdedSignatureContainer;
import com.bw.itext.pdf.utils.ExternalEmptySignatureContainer;
import com.bw.itext.pdf.utils.SignInfo;
import com.itextpdf.io.codec.Base64;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.UnitValue;
import com.itextpdf.signatures.*;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.Streams;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.*;
import java.util.List;


/**
 * @author Administrator
 */
@Slf4j
@NoArgsConstructor
public class PdfService {



    private PdfProperties pdfProperties;

    public PdfService(PdfProperties pdfProperties) {
        this.pdfProperties = pdfProperties;
        log.info("pdf config info:{}", this.pdfProperties);
    }

    String fieldNameA = "SIGNATURE_A";
    String fieldNameB = "SIGNATURE_B";

    /**
     * 签名方法
     *
     * @param pdfFileName     pdf名称
     * @param pdfSignFileName 签名pdf名称
     */
    public void sign(String pdfFileName, String pdfSignFileName, int y,String signPicName) throws IOException {
        sign(pdfFileName, "签章", "北京八维", pdfSignFileName, pdfProperties.getX(), y, pdfProperties.getWidth(), pdfProperties.getHeight(), signPicName);
    }

    public void sign(String pdfFileName, String pdfSignFileName) throws IOException {
        sign(pdfFileName, "签章", "北京八维", pdfSignFileName, pdfProperties.getX(), pdfProperties.getY(), pdfProperties.getWidth(), pdfProperties.getHeight(),pdfProperties.getSignPicName());
    }

    /**
     * 签名方法
     *
     * @param pdfFileName     pdf名称
     * @param reason          签名说明
     * @param location        签名机构
     * @param pdfSignFileName 签名pdf名称
     * @param x               坐标：x
     * @param y               坐标：y
     * @param width           签名图片宽
     * @param height          签名图片高
     */
    public void sign(String pdfFileName, String reason, String location, String pdfSignFileName, int x, int y, int width, int height,String signPicName) throws IOException {
        this.fieldNameA = this.fieldNameA + UUID.randomUUID();
        DeferredSignPdfAssLry signPdfAss = new DeferredSignPdfAssLry();
        Rectangle rectangle = new Rectangle(x, y, width, height);
        SignInfo signInfo = new SignInfo(rectangle, 1, reason, location, fieldNameA, pdfProperties.getPath() + signPicName, true);
        //获取空的签名文件
        ExternalEmptySignatureContainer externalSignatureContainer = new ExternalEmptySignatureContainer(signPdfAss.getDicFilter(), signPdfAss.getDicSubfilter(), signPdfAss.getHashAlgorithm());
        //给自己签章，重命名问题件
        if (pdfFileName.equals(pdfSignFileName)) {
            File file = new File(pdfProperties.getPath() + pdfFileName);
            Files.move(Paths.get(pdfProperties.getPath() + pdfFileName), Paths.get(pdfProperties.getPath() + "move" + pdfFileName));
            pdfFileName = "move" + pdfFileName;
        }
        //初始化
        try {
            signPdfAss.initSigner(Streams.readAll(new FileInputStream(pdfProperties.getPath() + pdfFileName)), pdfProperties.getPath() + pdfSignFileName + "_empty.pdf", signInfo, externalSignatureContainer);
        } catch (IOException | GeneralSecurityException e) {
            System.out.println("初始化签名对象异常");
            e.printStackTrace();
        }
        //待签名哈希,发给外部服务器进行签名
        String ToSignHash = Base64.encodeBytes(externalSignatureContainer.getDigestHash());
        String signedHash = null;//cfca签名后的哈希值
        try {
            signedHash = signBase64(ToSignHash, signPdfAss.getHashAlgorithm());
        } catch (GeneralSecurityException e) {
            System.out.println("签名异常！");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("签名异常！");
            e.printStackTrace();
        }
        //验签，这一步需要通过外部服务器返回的signedHash，与原文件哈希值externalSignatureContainer.getDigestHash()进行验证

        byte[] cmsInfo = Base64.decode(signedHash);
        try {
            DeferredSignPdfAssLry.embedSignature(pdfProperties.getPath() + pdfSignFileName + "_empty.pdf", pdfProperties.getPath() + pdfSignFileName, fieldNameA, new EmbdedSignatureContainer(cmsInfo));
            //删除临时文件
            File file = new File(pdfProperties.getPath() + pdfSignFileName + "_empty.pdf");
            if (file.exists()) {
                file.delete();
            }
        } catch (IOException e) {
            System.out.println("分离签名异常！");
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            System.out.println("分离签名异常！");
            e.printStackTrace();
        }
    }

    //签名方法
    public void sign(String src, String dest,
                     Certificate[] chain,
                     PrivateKey pk, String digestAlgorithm, String provider,
                     MakeSignature.CryptoStandard subfilter,
                     String reason, String location, String imagePath)
            throws GeneralSecurityException, IOException, DocumentException {
        // Creating the reader and the stamper
        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
        // Creating the appearance
        com.itextpdf.text.pdf.PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        com.itextpdf.text.Rectangle rectangle = new com.itextpdf.text.Rectangle(pdfProperties.getX(), pdfProperties.getY(), pdfProperties.getWidth(), pdfProperties.getHeight());
        appearance.setVisibleSignature(rectangle, 1, "sig");
        //实现把电子签章添加到pdf中
        Image image = Image.getInstance(imagePath);
        appearance.setSignatureGraphic(image);
        // 设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
        // Creating the signature
        ExternalDigest digest = new com.itextpdf.text.pdf.security.BouncyCastleDigest();
        ExternalSignature signature = new com.itextpdf.text.pdf.security.PrivateKeySignature(pk, digestAlgorithm, provider);
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
    }

    public void signPlus(String SRC, String DEST) throws GeneralSecurityException, IOException, DocumentException {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(Files.newInputStream(Paths.get(pdfProperties.getPdfKs())), pdfProperties.getPdfKsPass().toCharArray());
        String alias = (String) ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, pdfProperties.getPdfKsPass().toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        sign(SRC, String.format(DEST, 1), chain, pk, com.itextpdf.text.pdf.security.DigestAlgorithms.SHA256, provider.getName(), MakeSignature.CryptoStandard.CMS, "Test 1", "Ghent", pdfProperties.getPath() + "\\sign.png");
    }


    /**
     * 电子签章
     *
     * @param toSignHash
     * @param hashAlgorithm
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     */
    private String signBase64(String toSignHash, String hashAlgorithm) throws GeneralSecurityException, IOException {
        BouncyCastleProvider providerBC = new BouncyCastleProvider();
        Security.addProvider(providerBC);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(new FileInputStream(pdfProperties.getPdfKs()), pdfProperties.getPdfKsPass().toCharArray());
        String alias = ks.aliases().nextElement();
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKey pk = (PrivateKey) ks.getKey(alias, pdfProperties.getPdfKsPass().toCharArray());
        PrivateKeySignature signature = new PrivateKeySignature(pk, hashAlgorithm, "BC");
        BouncyCastleDigest digest = new BouncyCastleDigest();

        PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
        byte hash[] = Base64.decode(toSignHash);
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, null, null);
        byte[] extSignature = signature.sign(sh);
        sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
        ITSAClient tsaClient = new TSAClientBouncyCastle(SignInfo.TSA_URL, "", "", 4096, "SHA-512");
        byte[] pck7 = sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CMS, tsaClient, null, null);
        return Base64.encodeBytes(pck7);
    }


    /**
     * 下载文件
     *
     * @param fileName
     * @param response
     * @throws FileNotFoundException
     */
    public void download(String fileName, HttpServletResponse response) throws FileNotFoundException {
        String pdfPath = pdfProperties.getPath() + fileName;
        File file = new File(pdfPath);
        OutputStream os = null;
        try {
            FileInputStream in = new FileInputStream(file);
            BufferedInputStream bf = new BufferedInputStream(in);
            os = response.getOutputStream();
            response.setContentType(MediaType.APPLICATION_PDF_VALUE);
            response.setHeader("content-Disposition", "attachment;filename=" + fileName);
            byte[] b = new byte[bf.available() + 1000];
            int i;
            while ((i = bf.read(b)) != -1) {
                os.write(b, 0, i);
            }
            os.flush();
        } catch (IOException exception) {
            exception.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * pdf预览
     *
     * @param pdfName
     * @param response
     */
    public void preview(String pdfName, HttpServletResponse response) throws IOException {
        String filePath = pdfProperties.getPath() + pdfName;
        if (pdfName != null) {
            response.setContentType(MediaType.APPLICATION_PDF_VALUE);
            File file = new File(filePath);
            FileInputStream fis = new FileInputStream(file);
            ServletOutputStream out = response.getOutputStream();
            byte[] buf = new byte[4096];
            int len = -1;
            while ((len = fis.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            out.flush();
            fis.close();
            out.close();
        }
    }


    public <T> void exportSinge(T data, String fileName, String title, Class<T> cls) throws Exception {

        //创建pdf对象
        PdfDocument pdf = new PdfDocument(new PdfWriter(pdfProperties.getPath() + fileName));
        //文档设置
        Document document = new Document(pdf);
        PdfFont headerFont = PdfFontFactory.createFont(StandardFonts.TIMES_BOLD);
        PdfFont cellFont = PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN);
        PdfFont font = PdfFontFactory.createFont(pdfProperties.getFont() + ",0", PdfEncodings.IDENTITY_H, false);
        document.add(new Paragraph(title).setTextAlignment(TextAlignment.CENTER).setFont(font));

        //----
        //读取注解

        List<Map<String, Object>> list = new ArrayList<>();
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            PdfValue pd = declaredField.getDeclaredAnnotation(PdfValue.class);
            if (pd != null) {
//                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(declaredField.getName(), cls);
                //获取属性值
//                Object invoke = propertyDescriptor.getReadMethod().invoke(object);
                //暴力反射setAccessible后，可以调用私有方法
                declaredField.setAccessible(true);
                Object fvalue = declaredField.get(data);
                Map<String, Object> map = new HashMap<>();
                map.put("fieldName", declaredField.getName());
                map.put("rowName", pd.colName());
                map.put("fieldValue", fvalue);
                list.add(map);
            }

        }
        //读取PdfValue注解
        Field[] fields = cls.getDeclaredFields();
        List<PdfValue> pdfValues = new ArrayList<>();
        List<Field> fieldList = new ArrayList<>();
        for (Field field : fields) {
            PdfValue pdfValue = field.getDeclaredAnnotation(PdfValue.class);
            if (pdfValue != null) {
                pdfValues.add(pdfValue);
                fieldList.add(field);
            }
        }
        Table table = new Table(pdfValues.size());
        table.setWidth(UnitValue.createPercentValue(100));
        // adding rows
        Map<String, Object> dataMap = BeanUtil.beanToMap(data);
        for (int i = 0; i < pdfValues.size(); i++) {
            table.addCell(new Cell().add(new Paragraph(pdfValues.get(i).colName()).setFont(font)));
            table.addCell(new Cell().add(new Paragraph(String.valueOf(dataMap.get(fieldList.get(i).getName()))).setFont(font)));
        }
        document.add(table);
        document.close();
        document.close();
    }


    /**
     * pdf导出
     *
     * @param dataList
     * @param fileName
     * @param title
     * @param cls
     * @throws IOException
     */
    public <T> void export(List<T> dataList, String fileName, String title, Class<T> cls) throws IOException {
        try {
            //创建pdf对象
            PdfDocument pdf = new PdfDocument(new PdfWriter(pdfProperties.getPath() + fileName));
            //文档设置
            Document document = new Document(pdf);
            PdfFont headerFont = PdfFontFactory.createFont(StandardFonts.TIMES_BOLD);
            PdfFont cellFont = PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN);
            PdfFont font = PdfFontFactory.createFont(pdfProperties.getFont() + ",0", PdfEncodings.IDENTITY_H, false);
            //读取PdfValue注解
            Field[] fields = cls.getDeclaredFields();
            List<PdfValue> pdfValues = new ArrayList<>();
            List<Field> fieldList = new ArrayList<>();
            for (Field field : fields) {
                PdfValue pdfValue = field.getDeclaredAnnotation(PdfValue.class);
                if (pdfValue != null) {
                    pdfValues.add(pdfValue);
                    fieldList.add(field);
                }
            }
            document.add(new Paragraph(title).setTextAlignment(TextAlignment.CENTER).setFont(font));
            //创建表格
            Table table = new Table(pdfValues.size());
            table.setWidth(UnitValue.createPercentValue(100));
            //生成表头
            pdfValues.forEach(pdfValue -> {
                table.addHeaderCell(new Cell(1, 1)
                        .setTextAlignment(TextAlignment.CENTER)
                        .setBackgroundColor(ColorConstants.LIGHT_GRAY)
                        .add(new Paragraph(pdfValue.colName())
                                .setFont(font)));
            });
            // adding rows
            dataList.forEach(data -> {
                Map<String, Object> dataMap = BeanUtil.beanToMap(data);
                fieldList.forEach(field -> {
                    table.addCell(new Cell().add(new Paragraph(String.valueOf(dataMap.get(field.getName()))).setFont(font)));
                });
            });
            document.add(table);
            document.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
